#include <cjsonparser.h>
#include <cjson.h>
#include <cjsondebug.h>

using namespace std;

template <class T>
void clean_up_list(std::list <T*> l)
{
  while (!l.empty())
  {
    if (l.back())
    {
      delete l.back();
      l.pop_back();
    }
  }
}

CJsonParser::CJsonPare::CJsonPare() :
  pair()
{
}

CJsonParser::CJsonPare::CJsonPare(jstring f, CJsonValue * s) :
  pair(f,s)
{
}

CJsonParser::CJsonPare::~CJsonPare()
{
  if (second)
  {
    delete second;
  }
}

CJsonParser::fifo_node::fifo_node()
{
  obj = NULL;
}

CJsonParser::fifo_node::fifo_node(CJsonValue *obj, CJsonPare *pare)
{
  this->obj = obj;
  pares_fifo.push_back(pare);
}

CJsonParser::fifo_node::~fifo_node()
{
  clean_up_list<CJsonPare>(pares_fifo);
  if (obj)
  {
    delete obj;
  }
}

long CJsonParser::small_pow(long base, unsigned grade)
{
  long result = 1;
  for (unsigned i = 0; i < grade; i++)
  {
    result *= base;
  }
  return result;
}

jint CJsonParser::number_from_list(list <unsigned short> & temp_number_value)
{
  jint temp_number = 0;
  for (size_t i = 0; !temp_number_value.empty(); i++)
  {
    temp_number += temp_number_value.back() * small_pow(10, i);
    temp_number_value.pop_back();
  }
  return temp_number;
}

CJsonValue * CJsonParser::recognize_unknown(jstring & temp_string)
{
  if (temp_string == JSON_TRUE)
  {
    return new CJsonValueBool(true);
  }
  else if (temp_string == JSON_FALSE)
  {
    return new CJsonValueBool(false);
  }
  else if (temp_string == JSON_NULL)
  {
    return new CJsonValueNull();
  }
  else
  {
    throw CJsonError(JE_PARSE_FAILED);
  }
  return NULL;
}

void CJsonParser::clean_up_before_error(CJsonObject *o, std::list <fifo_node*> l)
{
  if (o)
  {
    delete o;
  }
  clean_up_list <fifo_node> (l);
}

CJsonObject * CJsonParser::Execute(jstring & json_string)
{
  CDebugLog debug_log(LOG_FILE_NAME);
  debug_log << J(">> CJsonParse::ParseJson\n");
  enum CJsonParseFlags
  {
    PF_BEGIN         =0 ,
    PF_NAME_START   ,
    PF_NAME_END     ,
    PF_STRING_START ,
    PF_STRING_END   ,
    PF_VALUE        ,
    PF_COLON        ,
    PF_ARRAY_END    ,
    PF_OBJECT_END   ,
    PF_NUMBER_END   , 
    PF_UNKNOWN_END  ,
    PF_NEXT_OR_END
  } flags = PF_BEGIN;

  list < fifo_node * > fifo;
  CJsonObject *json_object = NULL;
  list <unsigned short> temp_number_value;
  jstring temp_string;
  jchar string_quote;
  debug_log << J("Starting loop\n");
  for (unsigned i = 0; i < json_string.size(); i++)
  {
    debug_log << J("------------------------------\n") 
              << J("iter ") << i << J(" (Flags: ") << (int)flags << J(")\n")
              << J("temp_string: ") << temp_string << J("\n")
              << J("fifo size: ") << fifo.size() << J("\n");
    switch (json_string[i])
    {
      case J('{'): //starting new object
        debug_log << J("{\n");
        switch (flags)
        {
          case PF_BEGIN:
          case PF_VALUE:
            debug_log << J("O--> New Obj started\n");
            debug_log << J("P--> New Pare Added\n");
            fifo.push_back( new fifo_node(new CJsonObject(), new CJsonPare()) );
            flags = PF_NAME_START;
          case PF_STRING_END:
          case PF_NAME_END:
            temp_string += json_string[i];
          break;
          default:
            clean_up_before_error(json_object, fifo);
            throw CJsonError(JE_PARSE_FAILED);
        }
      break;
      case J('['): //starting new array
        debug_log << J("[\n");
        switch (flags)
        {
          case PF_VALUE:
            debug_log << J("A--> New Array started\n");
            fifo.push_back( new fifo_node((CJsonValue*)(new CJsonArray()), new CJsonPare()) );
            flags = PF_VALUE;
          case PF_STRING_END:
          case PF_NAME_END:
            temp_string += json_string[i];
          break;
          default:
            clean_up_before_error(json_object, fifo);
            throw CJsonError(JE_PARSE_FAILED);
        }
      break;
      case J('\''): //some string
      case J('"'): 
        debug_log << J("\"\n");
        switch (flags)
        {
          case PF_NAME_START:
            temp_string.clear();
            string_quote = json_string[i];
            flags = PF_NAME_END;
          break;
          case PF_STRING_END:
            if (fifo.back()->pares_fifo.empty())
            {
              throw CJsonError(JE_PARSE_FAILED);
            }
            else
            {
              if (string_quote == json_string[i])
              {
                fifo.back()->pares_fifo.back()->second = new CJsonValueString(temp_string);
                debug_log << J("V--> New Value (") << temp_string << J(")\n");
                flags = PF_NEXT_OR_END;
              }
              else
              {
                temp_string += json_string[i];
              }
            }
          break;
          case PF_NAME_END:
            if (fifo.back()->pares_fifo.empty())
            {
              throw CJsonError(JE_PARSE_FAILED);
            }
            else
            {
              if (string_quote == json_string[i])
              {
                fifo.back()->pares_fifo.back()->first = temp_string;
                debug_log << J("V--> New Name (") << temp_string << J(")\n");
                flags = PF_COLON;
              }
              else
              {
                temp_string += json_string[i];
              }
            }
          break;
          case PF_VALUE:
            temp_string.clear();
            string_quote = json_string[i];
            flags = PF_STRING_END;
          break;
          default:
            clean_up_before_error(json_object, fifo);
            throw CJsonError(JE_PARSE_FAILED);
        }
      break;
      case J(':'): //colon
        debug_log << J(":\n");
        switch (flags)
        {
          case PF_STRING_END:
          case PF_NAME_END:
            temp_string += json_string[i];
          break;
          case PF_COLON:
            flags = PF_VALUE;
          break;
          default:
            clean_up_before_error(json_object, fifo);
            throw CJsonError(JE_PARSE_FAILED);
        }
      break;
      case J(','): //comma
        debug_log << J(",\n");
        switch (flags)
        {
          case PF_STRING_END:
          case PF_NAME_END:
            temp_string += json_string[i];
          break;
          case PF_NUMBER_END:
            fifo.back()->pares_fifo.back()->second = new CJsonValueNumber(number_from_list(temp_number_value));
          case PF_UNKNOWN_END:
            if (flags == PF_UNKNOWN_END)
            {
              fifo.back()->pares_fifo.back()->second = recognize_unknown(temp_string);
            }
          case PF_NEXT_OR_END:
            debug_log << J("P--> New Pare Added\n");
            fifo.back()->pares_fifo.push_back(new CJsonPare());
            debug_log << J("obj type: ") << (int)fifo.back()->obj->GetType() << J("\n");
            if (fifo.back()->obj->GetType() == JV_ARRAY)
            {
              flags = PF_VALUE;
            }
            else
            {
              flags = PF_NAME_START;
            }
          break;
          default:
            clean_up_before_error(json_object, fifo);
            throw CJsonError(JE_PARSE_FAILED);
        }
      break;
      case J('}'): //object end
        debug_log << J("}\n");
        debug_log << J("(") << (int)flags << J(")\n");
        switch (flags)
        {
          case PF_STRING_END:
          case PF_NAME_END:
            temp_string += json_string[i];
          break;
          case PF_NUMBER_END:
            fifo.back()->pares_fifo.back()->second = new CJsonValueNumber(number_from_list(temp_number_value));
          case PF_UNKNOWN_END:
            if (flags == PF_UNKNOWN_END)
            {
              fifo.back()->pares_fifo.back()->second = recognize_unknown(temp_string);
            }
          case PF_NEXT_OR_END:
          {
            fifo_node *node = fifo.back();
            fifo.pop_back();
            while (!node->pares_fifo.empty())
            {
              ((CJsonObject*)node->obj)->AddPare(node->pares_fifo.front()->first, node->pares_fifo.front()->second);
              delete node->pares_fifo.front();
              node->pares_fifo.pop_front();
            }
            if (fifo.empty())
            {
              json_object = new CJsonObject((CJsonObject*)node->obj);
              delete node;
              debug_log << J("END FOUND\n");
              break;
            }
            else
            {
              fifo.back()->pares_fifo.back()->second = (CJsonValue*)(new CJsonObject((CJsonObject*)node->obj));
              debug_log << J("Obj added (") << node->obj->ToString() << J(")\n");
              delete node;
            }
            debug_log << J("O--> Obj END\n");
            flags = PF_NEXT_OR_END;
          }
          break;
          default:
            clean_up_before_error(json_object, fifo);
            throw CJsonError(JE_PARSE_FAILED);
        }
      break;
      case J(']'): //array end
        debug_log << J("]\n");
        debug_log << J("(") << (int)flags << J (")\n");
        switch (flags)
        {
          case PF_STRING_END:
          case PF_NAME_END:
            temp_string += json_string[i];
          break;
          case PF_NUMBER_END:
            fifo.back()->pares_fifo.back()->second = new CJsonValueNumber(number_from_list(temp_number_value));
          case PF_UNKNOWN_END:
            if (flags == PF_UNKNOWN_END)
            {
              fifo.back()->pares_fifo.back()->second = recognize_unknown(temp_string);
            }
          case PF_NEXT_OR_END:
          {
            fifo_node *node = fifo.back();
            fifo.pop_back();
            while (!node->pares_fifo.empty())
            {
              if (node->obj->GetType() == JV_ARRAY)
              {
                ((CJsonArray*)node->obj)->AddMember(node->pares_fifo.front()->second);
              }
              else
              {
                ((CJsonObject*)node->obj)->AddPare(node->pares_fifo.front()->first, node->pares_fifo.front()->second);
              }
              delete node->pares_fifo.front();
              node->pares_fifo.pop_front();
            }
            if (fifo.empty())
            {//TODO: Add array returning here
              /*json_object = node->obj;
              delete node;
              //fprintf(f, J("END FOUND\n"));
              break;*/
              //fprintf(f, "!!! FIFO EMPTY !!!\n");
              delete node;
            }
            else
            {
              fifo.back()->pares_fifo.back()->second = (CJsonValue*)(new CJsonArray((CJsonArray*)node->obj));
              debug_log << J("Array added (") << node->obj->ToString() << (")\n");
              delete node;
            }
            debug_log << J("O--> Array END\n");
            flags = PF_NEXT_OR_END;
          }
          break;
          default:
            clean_up_before_error(json_object, fifo);
            throw CJsonError(JE_PARSE_FAILED);
        }
      break;
      case J(' '): //space
        debug_log << J("' '\n");
        switch (flags)
        {
          case PF_STRING_END:
          case PF_NAME_END:
            temp_string += json_string[i];
          break;
          case PF_NUMBER_END:
            fifo.back()->pares_fifo.back()->second = new CJsonValueNumber(number_from_list(temp_number_value));
            flags = PF_NEXT_OR_END;
          break;
          case PF_UNKNOWN_END:
            fifo.back()->pares_fifo.back()->second = recognize_unknown(temp_string);
            flags = PF_NEXT_OR_END;
          break;
          default:{}
            //no error
        }
      break;
      case J('0'):
      case J('1'):
      case J('2'):
      case J('3'):
      case J('4'):
      case J('5'):
      case J('6'):
      case J('7'):
      case J('8'):
      case J('9'):
        debug_log << J("numbers (") << json_string[i] << J(")\n");
        switch (flags)
        {
          case PF_VALUE:
            temp_number_value.clear();
            temp_number_value.push_back(json_string[i] - J('0'));
            flags = PF_NUMBER_END;
          break;
          case PF_STRING_END:
          case PF_NAME_END:
            temp_string += json_string[i];
          break;
          case PF_NUMBER_END:
            temp_number_value.push_back(json_string[i] - J('0'));
          break;
          default:
            clean_up_before_error(json_object, fifo);
            throw CJsonError(JE_PARSE_FAILED);
        }
      break;
      case J('\0'):
      {
        debug_log << J("END\n");
        break;
      }
      break;
      default:
        debug_log << J("default (") << json_string[i] << J(")\n");
        switch (flags)
        {
          case PF_VALUE:
            temp_string.clear();
            temp_string += json_string[i];
            flags = PF_UNKNOWN_END;
          break;
          case PF_UNKNOWN_END:
          case PF_STRING_END:
          case PF_NAME_END:
            temp_string += json_string[i];
          break;
          case PF_NUMBER_END:
            temp_number_value.push_back(json_string[i] - J('0'));
          break;
          default:
            clean_up_before_error(json_object, fifo);
            throw CJsonError(JE_PARSE_FAILED);
        }
    }
  }
  debug_log << J("<< CJsonParser::ParseJson\n");
  clean_up_list<fifo_node>(fifo);
  return json_object;
}

